<template>
  <div class="container">
    <modal ref="modal" :then="modalThen" :args="args" :title="title" :content="content"></modal>
    <slot></slot>
    <msg></msg>
    <div class="uploader-panel"
         :style="{bottom:minimize ? '-354px':'',display:circleMiniMize && minimize ? 'none':''}"
         v-if="panelShow && files.length > 0">
      <div class="up-header">
        <span @click="t" title="暂停所有任务" style="color: #666;font-weight: 200;font-size: 14px;cursor: pointer">{{files.filter(e=>{return e.flag !== 3}).length < 1 ? '上传完成':`正在上传(${files.filter(e=>{return e.flag === 3}).length}/${files.length})`}}</span>
        <div style="flex: 1 1;text-align: end;display: flex;align-items: center;justify-content: flex-end;">
          <svg v-if="!minimize" @click="onMinimize(true)" viewBox="0 0 1030 1024" version="1.1"
               xmlns="http://www.w3.org/2000/svg" p-id="5479" width="14" height="14" class="icon"
               style="display: inline-block; margin-right: 7px;cursor: pointer">
            <path data-v-21e3517a=""
                  d="M972.8 566.784H64c-32 0-64-25.6-64-57.6s32-51.2 64-51.2h908.8c32 0 57.6 25.6 57.6 51.2s-25.6 57.6-57.6 57.6z"
                  p-id="5480" fill="#707070"></path>
          </svg>
          <svg v-else @click="onMinimize(false)" class="icon" viewBox="0 0 1024 1024" version="1.1"
               xmlns="http://www.w3.org/2000/svg" p-id="2632" width="32" height="32">
            <path
              d="M316 625.6c-8.8-8.8-8.8-22.4 0-30.4l184-184c8.8-8.8 22.4-8.8 30.4 0s8.8 22.4 0 30.4l-184 184c-8 8-22.4 8-30.4 0z"
              p-id="2633" fill="#707070"></path>
            <path
              d="M500 410.4c8.8-8.8 22.4-8.8 30.4 0l184 184c8.8 8.8 8.8 22.4 0 30.4s-22.4 8.8-30.4 0l-184-184c-8.8-8-8.8-21.6 0-30.4z"
              p-id="2634" fill="#707070"></path>
          </svg>
          <svg @click="showModal(closePanel,'是否继续','关闭面板将会结束清空所有任务')" viewBox="0 0 1024 1024" version="1.1"
               xmlns="http://www.w3.org/2000/svg" width="22" height="22"
               class="remove-file-icon"
               style="display: inline-block; margin-right: 7px;cursor: pointer"
          >
            <path
              d="M517.08411984 479.11475483L301.90637831 263.93753776a26.85237631 26.85237631 0 1 0-37.98667221 37.95153335l215.17669262 215.19504873L263.91970607 732.27864411a26.85237631 26.85237631 0 1 0 37.96936504 37.96884057l215.19504878-215.17669264 215.19504866 215.17669264a26.85237631 26.85237631 0 1 0 37.9688406-37.95100889l-215.17669262-215.2123559 215.17669262-215.1772171a26.85237631 26.85237631 0 1 0-37.9688406-37.96936505L517.10195147 479.11475483z"
              fill="#707070"></path>
          </svg>
        </div>
      </div>
      <div class="up-items">
        <div class="up-items-info">
          <span class="filename">文件名</span>
          <span class="size">大小</span>
          <span class="speed">上传速度</span>
          <span class="state">状态</span>
          <div class="up-actions"/>
        </div>
        <div class="up-item" v-for="(i,index) in files">
          <span class="filename" :title="i.name" v-text="i.name"></span>
          <span class="size" :title="i.size" v-text="fileSizeConvert(i.size)"></span>
          <span class="speed" :title="i.speed" v-text="i.speed"></span>
          <span class="state" :title="i.state" v-text="i.state"></span>
          <div class="progress" style="transform: translateX(-100%)"
               :class="{'md5':i.flag === 1,'up':i.flag === 2,'success':i.flag === 3,'error':i.flag === 4}"
               :style="`transform: translateX(${i.progress});top:${18 + index * 34}px`"></div>
          <div class="up-actions">

            <svg @click="pause(index)" v-if="i.up && i.flag !== 4" t="1620043337343" class="pause"
                 viewBox="0 0 1024 1024" version="1.1"
                 xmlns="http://www.w3.org/2000/svg" p-id="1748" width="20" height="20">
              <path
                d="M688.64 168.96c-40.96 0-74.24 35.84-74.24 74.24v552.96c0 40.96 35.84 74.24 74.24 74.24s74.24-35.84 74.24-74.24V243.2c0-40.96-35.84-74.24-74.24-74.24z m-353.28 0c-40.96 0-74.24 35.84-74.24 74.24v552.96c0 40.96 35.84 74.24 74.24 74.24s74.24-35.84 74.24-74.24V243.2c2.56-40.96-33.28-74.24-74.24-74.24z"
                p-id="1749" fill="#707070"></path>
            </svg>

            <svg @click="continueUp(index)" v-else-if="!i.up && i.flag !== 3 && i.flag !== 4"
                 t="1620043443115"
                 class="icon" viewBox="0 0 1024 1024" version="1.1"
                 xmlns="http://www.w3.org/2000/svg" p-id="2760" width="14" height="14">
              <path
                d="M183.296 10.688l748.8 432.448a79.168 79.168 0 0 1 0 137.472l-748.8 432.64A79.552 79.552 0 0 1 64 944.384V79.616A79.616 79.616 0 0 1 183.296 10.688z"
                p-id="2761" fill="#707070"></path>
            </svg>

            <svg @click="continueUp(index)" v-if="i.upError" style="margin-left: 5px;margin-right: 3px"
                 t="1620043545528" class="icon"
                 viewBox="0 0 1084 1024" version="1.1"
                 xmlns="http://www.w3.org/2000/svg" p-id="4128" width="16" height="16">
              <path
                d="M1034.649641 382.928223h-382.919926l143.148232-143.148232a380.430947 380.430947 0 0 0-270.788208-112.131719c-102.30344 0-198.480162 39.823672-270.788208 112.131719a380.430947 380.430947 0 0 0-112.131718 270.788208 380.367127 380.367127 0 0 0 112.131718 270.724388 380.430947 380.430947 0 0 0 270.788208 112.131718 380.367127 380.367127 0 0 0 288.147245-130.767155l96.049082 84.050924a510.559902 510.559902 0 1 1-23.230476-697.169546l149.594051-149.530231v382.919926z"
                p-id="4129" fill="#707070"></path>
            </svg>

            <svg @click.stop="showModal(delTask,'是否继续','确定删除这个任务吗',[i,index])" viewBox="0 0 1024 1024"
                 version="1.1"
                 xmlns="http://www.w3.org/2000/svg" p-id="3136" width="22" height="22"
                 class="remove-file-icon">
              <path
                d="M517.08411984 479.11475483L301.90637831 263.93753776a26.85237631 26.85237631 0 1 0-37.98667221 37.95153335l215.17669262 215.19504873L263.91970607 732.27864411a26.85237631 26.85237631 0 1 0 37.96936504 37.96884057l215.19504878-215.17669264 215.19504866 215.17669264a26.85237631 26.85237631 0 1 0 37.9688406-37.95100889l-215.17669262-215.2123559 215.17669262-215.1772171a26.85237631 26.85237631 0 1 0-37.9688406-37.96936505L517.10195147 479.11475483z"
                p-id="3137" fill="#707070"></path>
            </svg>
          </div>
        </div>

      </div>
    </div>
    <div draggable="true" class="circle-panel" v-if="circleMiniMize"
         :style="{top:top?top:'',left:left?left:'',display:minimize ? '' :'none'}"
         @mousedown="mouseDown = true" @mouseleave="dragCircle" @mouseup="mouseDown = false" title="展开面板"
         @click="onMinimize(false)">
      <div class="circle-progress"></div>
      <div style="width: 100%;height: 100%;display: flex;align-items: center;justify-content: center">
        <div class="circle-msg" :style="circleMiniMizeChange === 1?'':'display:none'"></div>
        <div :style="circleMiniMizeChange === 2?'':'display:none'" class="up-info">
          {{`共${files.length}-已完成${files.filter(e=>{return e.flag === 3}).length}`}}
        </div>
        <div :style="circleMiniMizeChange === 3?'':'display:none'" class="speed-watch"></div>
      </div>

    </div>
  </div>

</template>

<script>
  import SparkMD5 from './sparkMd5';
  import modal from './modal'
  import req from "./request";
  import msg from './message'

  let speed = 0;
  export default {
    name: "uploader",
    components: {
      modal, msg
    },
    data() {
      return {
        currentIndex: 0,
        task_amount: 0,
        status: 0,
        minimize: false,
        modalThen: null,
        args: [],
        circleMiniMizeTimer: undefined,
        circleMiniMizeChange: 1,
        mouseDown: false,
        top: undefined,
        left: undefined,
        files: [],
        title: '',
        content: ''
      }
    },
    props: {
      piece: {
        type: Number, default: 1024 * 1024
      },
      circleMiniMize: {
        type: Boolean,
        default() {
          return false;
        }
      },
      state: {
        type: Object,
        default() {
          return {
            md5: '文件md5计算中',
            up: '上传中',
            pause: '暂停',
            error: '上传出错',
            success: '上传完成'
          }
        }
      },
      target: {
        type: String,
        default() {
          return '/u';
        }
      },
      fileParameterName: {
        type: String,
        default() {
          return "file";
        }
      },
      testChunks: {
        type: Boolean,
        default() {
          return false;
        }
      },
      checkChunkUploadedByResponse: {
        type: Function,
        default() {
          return () => {
          };
        }
      },
      header: {
        type: Object,
        default() {
          return {};
        }
      },
      panelShow: {
        type: Boolean,
        default() {
          return true;
        }
      },
      multiple: {
        type: Boolean,
        default() {
          return false;
        }
      },
      fileAdded: {
        type: Function,
        default() {
          return () => {
          };
        }
      },
      fileSuccess: {
        type: Function,
        default() {
          return () => {
          };
        }
      },
      fileProgress: {
        type: Function,
        default() {
          return () => {
          };
        }
      },
      fileError: {
        type: Function,
        default() {
          return () => {
          };
        }
      },

    },
    // setup() {
    //     const files = ref([]);
    //     const title = ref('');
    //     const content = ref('');
    //
    //     function set(index, prop, value) {
    //         if (files.value[index]) {
    //             files.value[index][prop] = value;
    //             files.value.push([]);
    //             files.value.pop();
    //         }
    //     }
    //
    //     return {
    //         files, set, title, content
    //     }
    // },
    destroyed() {
      this.files.forEach(e => {
        clearInterval(e.timer);
      });
      clearInterval(this.circleMiniMizeTimer)
    },
    methods: {
      set(index, prop, value) {
        if (this.files[index]) {
          this.files[index][prop] = value;
          this.files.push([]);
          this.files.pop();
        }
      },
      dragCircle(e) {
        if (this.mouseDown) {
          this.top = e.clientY - 35 + 'px';
          this.left = e.clientX - 35 + 'px';
          this.mouseDown = false;
        }
      },
      showModal(method, title, content, args) {
        this.$refs['modal'].show = true;
        this.modalThen = method;
        this.title = title;
        this.content = content;
        this.args = args;
      },
      closePanel() {
        this.t();
        this.files = [];
      },
      onMinimize(e) {
        this.minimize = e;
      },
      t() {
        this.files.forEach((e, index) => {
          this.pause(index);
        });
      },
      pause(index) {
        if (this.files[index].flag === 1) {
          this.files[index].md5up = false;
        } else {
          if (this.files[index].req) {
            this.files[index].req.abort();
          }
        }

        clearInterval(this.files[index].timer);
        this.set(index, 'up', false);
        this.set(index, 'isCancel', true);
        this.set(index, 'upError', false);
      },
      continueUp(index) {
        this.files.forEach((e, index) => {
          this.pause(index);
        });
        this.currentIndex = index;
        this.set(index, 'up', true);
        this.set(index, 'isCancel', false);
        this.set(index, 'upError', false);
        if (this.files[index].flag === 1) {
          this.files[index].isComputeMD5 = false;
        }

        clearInterval(this.files[index].timer);
        this.execute();
      },
      delTask() {
        if (this.args[0].up) {
          this.pause(this.args[1]);
        }
        if (this.task_amount.flag !== 3) {
          this.task_amount--;
        }
        this.files.splice(this.args[1], 1);
      },
      addTask(data) {
        this.fileAdded(data);
        data.speed = '';
        this.files.push(data);
        this.task_amount++;
        if (this.status === 0) {
          this.nextTask();
          this.execute();
          if (this.circleMiniMize) {
            this.circleMiniMizeTimer = setInterval(() => {
              if (this.circleMiniMizeChange === 1) {
                this.circleMiniMizeChange = 2;
              } else if (this.circleMiniMizeChange === 2) {
                if (this.files[this.currentIndex].flag !== 1) {
                  this.circleMiniMizeChange = 3;
                } else this.circleMiniMizeChange = 1;

              } else if (this.circleMiniMizeChange === 3) {
                this.circleMiniMizeChange = 1;
              }
              document.querySelector('.circle-msg').innerText = this.files[this.currentIndex].state;
            }, 3000);
          }
        }
      },
      checkChunk(file) {
        const option = {};
        option.filename = file.name;
        option.action = `${this.target}/u?unique=${file.unique}`;
        option.header = this.header;
        option.method = 'GET';
        req(option);
        return option;
      },
      execute() {
        if (this.currentIndex === -1) {
          return;
        }
        this.status = 1;
        const file = this.files[this.currentIndex];
        file.flag = 1;
        file.up = true;
        this.computeMD5(this, file).then(e => {
          if (!file.chunks || file.chunks.length === 0) {
            this.slice(file);
          }
          const req = this.checkChunk(file);
          const this_ = this;
          req.onSuccess = function (data) {
            const check = [];
            file.chunks.forEach(e => {
              const result = this_.checkChunkUploadedByResponse(e, data);
              if (result === true) {
                check.push(e.offset);
              }
            });
            file.check = check;
            this_.upload(file);
          };
          req.onError = function (e) {
            msg.methods.addMsg('获取上传信息失败...' + e);
            this_.upError(file, file.chunks[0], '获取上传信息失败...' + e);
            clearInterval(this.circleMiniMizeTimer);
            this_.circleMiniMizeChange = 1;
          };


        }).catch(e => {
          //error
          msg.methods.addMsg('计算文件md5发生错误,请重试...' + e);
          this.upError(file, undefined, '计算文件md5发生错误,请重试...' + e);
          console.log(e)
          clearInterval(this.circleMiniMizeTimer);
          this.circleMiniMizeChange = 1;
        });
      },
      nextTask() {
        this.currentIndex = -1;
        for (let i = 0; i < this.files.length; i++) {
          if (this.files[i].flag !== 3) {
            this.currentIndex = i;
            break;
          }
        }
      },
      /**
       * 切片
       **/
      slice(file) {
        let piece = this.piece;
        let totalSize = file.size; // 文件总大小
        let start = 0; // 每次上传的开始字节
        let end = start + piece; // 每次上传的结尾字节
        let chunks = [];
        let offset = 0;
        while (start < totalSize) {
          // 根据长度截取每次需要上传的数据
          // File对象继承自Blob对象，因此包含slice方法
          let blob = file.slice(start, end);
          blob.startByte = start;
          blob.endByte = start + blob.size;
          blob.offset = offset++;
          chunks.push(blob);
          start = end;
          end = start + piece;
        }
        file.chunks = chunks;
      },

      /**
       * 上传
       * */
      upload(file) {
        const chunks = file.chunks;
        const tasks = [];
        let count = 0;
        file.loaded = 0;
        for (let i = 0; i < chunks.length; i++) {
          if (file.check.indexOf(i) === -1) {
            const data = {};
            data.current = chunks[i];
            const formdata = new FormData();
            formdata.append(this.fileParameterName, chunks[i]);
            data.formData = formdata;
            data.currentChunk = i + '';
            data.total = chunks.length;
            data.chunkNumber = i;
            tasks.push(data);
          } else {
            file.loaded += chunks[i].size;
          }
        }
        file.total = chunks.length;
        file.flag = 2;
        if (tasks.length > 0) {
          this.upSpeed();
          file.timer = setInterval(() => {
            this.upSpeed();
          }, 1000);
          this.upChunk(tasks, file, count);
        } else {
          this.merge(file);
        }

      },
      upSpeed() {
        let t = (speed / 1024).toFixed(2);
        t = t > 1024 ? (t / 1024).toFixed(2) + 'mb / s' : t + 'kb / s';
        this.set(this.currentIndex, 'speed', t);
        if (this.circleMiniMize) {
          document.querySelector(".speed-watch").innerHTML = t;
        }
        speed = 0;
      },
      /**
       * 合并文件
       **/
      merge(file) {
        const option = {}, this_ = this;
        option.action = `${this.target}/u`;
        option.header = this.header;
        option.data = JSON.stringify({
          filename: file.name,
          totalSize: file.size,
          identifier: file.unique,
          fileType: file.type,
          lastModified: file.lastModified,
          total: file.total
        });
        option.contentType = 'application/json';
        option.method = 'PUT';
        option.onSuccess = function (e) {
          this_.status = 0;
          this_.set(this_.currentIndex, 'flag', 3);
          this_.set(this_.currentIndex, 'up', false);
          this_.set(this_.currentIndex, 'state', this_.state.success);
          this_.fileSuccess(file, e);
          clearInterval(file.timer);
          if (this_.task_amount > 0) {
            this_.task_amount--;
            this_.nextTask();
            if (this_.currentIndex !== -1) {
              this_.execute();
            }
          }

          clearInterval(this_.circleMiniMizeTimer);
          if (this_.circleMiniMize) {
            this_.circleMiniMizeChange = 1;
            document.querySelector('.circle-msg').innerText = file.state;
          }
        };

        option.onError = function (e) {
          msg.methods.addMsg('文件合并失败...' + e);
          this_.upError(file, undefined, '文件合并失败...' + e);
          clearInterval(this_.circleMiniMizeTimer);
          if (this_.circleMiniMize) {
            this_.circleMiniMizeChange = 1;
            document.querySelector('.circle-msg').innerText = 'error';
          }
        };

        req(option);
      },
      /**
       * 上传单个分片
       **/
      upChunk(data, file, count) {
        let record = 0;
        let response;
        const option = {}, this_ = this;
        option.filename = file.name;
        option.action = `${this.target}/u?currentChunk=${data[count].currentChunk}
                &total=${data[count].total}&unique=${file.unique}&filename=${file.name}
                &chunkNumber=${data[count].chunkNumber}`;
        option.header = this.header;
        option.data = data[count].formData;
        option.method = 'POST';
        option.onProgress = function (e) {
          const loaded = e.loaded > data[count].current.size ? data[count].current.size : e.loaded;
          const c = loaded - record;
          file.loaded += c;
          speed += c;
          record = loaded;
          this_.fileProgress(file, data[count].current, e, parseInt(file.loaded / file.size * 100));
          this_.set(this_.currentIndex, 'state', parseInt(file.loaded / file.size * 100) + '%');
          this_.set(this_.currentIndex, 'progress', -100 + parseInt(file.loaded / file.size * 100) + '%');
          if (this_.circleMiniMize && this_.minimize) {
            document.querySelector('.circle-progress').style.height = parseInt(file.loaded / file.size * 100) + '%';
          }
        };

        option.onSuccess = function (e) {
          response = e;
          if (!file.isCancel) {
            if (data[count + 1]) {
              this_.upChunk(data, file, count + 1);
            } else {
              this_.merge(file);
            }
          }
        };

        option.onError = function (e) {
          msg.methods.addMsg(e);
          if (e.message !== 'cancel') {
            this_.upError(file, data[count].current, e);
          }
          clearInterval(this_.circleMiniMizeTimer);
          this_.circleMiniMizeChange = 1;
        };
        req(option);
        file.req = option.xhr;
      },
      /**
       * 计算md5，实现断点续传及秒传
       * @param file
       */
      computeMD5(this_, file) {
        return new Promise((resolve, reject) => {
          if (file.unique) {
            resolve();
            return;
          }
          if (file.isComputeMD5) {
            return;
          } else {
            file.md5up = true;
          }
          file.isComputeMD5 = true;
          if (this.circleMiniMize) {
            document.querySelector('.circle-msg').innerText = this_.state.md5;
            this_.circleMiniMizeChange === 1;
          }
          let fileReader = new FileReader();
          let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
          let record = this_.files[this_.currentIndex].md5Record;
          let currentChunk = record ? record : 0;
          const chunkSize = 1024 * 1024;
          let chunks = Math.ceil(this_.files[this_.currentIndex].size / chunkSize);
          if (!file.sparkMD5) {
            file.sparkMD5 = new SparkMD5.ArrayBuffer();
          }
          let spark = file.sparkMD5;

          loadNext();

          fileReader.onload = (e => {
            spark.append(e.target.result);
            if (currentChunk < chunks) {
              if (file.md5up) {
                loadNext();
              }

              // 实时展示MD5的计算进度
              this_.$nextTick(() => {
                let s = ((currentChunk / chunks) * 100).toFixed(0);
                setTimeout(() => {
                  file.progress = (-100 + parseInt(s)) + '%';
                  this_.set(this_.currentIndex, 'state', this_.state['md5'] + s + '%');
                  if (this.circleMiniMize) {
                    document.querySelector('.circle-progress').style.height = parseInt(s) + '%';
                  }
                }, 0);
              })
            } else {
              let md5 = spark.end();
              this_.computeMD5Success(md5, this_.files[this_.currentIndex]);
              resolve();
            }
          });
          fileReader.onerror = function () {
            reject();
          };

          function loadNext() {
            this_.files[this_.currentIndex].md5Record = currentChunk + 1;
            let start = currentChunk * chunkSize;
            let end = ((start + chunkSize) >= this_.files[this_.currentIndex].size)
              ? this_.files[this_.currentIndex].size
              : start + chunkSize;
            fileReader.readAsArrayBuffer(blobSlice.call(this_.files[this_.currentIndex], start, end));
            currentChunk++;
          }
        });
      },
      computeMD5Success(md5, file) {
        file.unique = md5;
      },
      fileSizeConvert(size) {
        size = (size / 1024).toFixed(2);
        if (size < 1024) {
          return size + 'kb';
        } else {
          size = (size / 1024).toFixed(2);
          if (size < 1024) {
            return size + 'mb';
          } else {
            return size = (size / 1024).toFixed(2) + 'Gb';
          }
        }

      }
      ,
      upError(file, chunk, msg) {
        this.status = 0;
        this.set(this.currentIndex, 'upError', true);
        this.set(this.currentIndex, 'flag', 4);
        this.set(this.currentIndex, 'state', msg);
        this.fileError(file, chunk, msg);
      }
    }
  }
</script>

<style scoped>
  .circle-panel {
    position: fixed;
    bottom: 15px;
    right: 5px;
    width: 70px;
    height: 70px;
    display: flex;
    border-radius: 100%;
    overflow: hidden;
    flex-direction: column-reverse;
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);
    background: aliceblue;
    cursor: pointer;
  }

  .circle-progress {
    background: #89c3ff;
    height: 0;
    width: 100%;
    position: absolute;
    z-index: -1;
    transition: .2s linear;
  }

  .up-info, .speed-watch, .circle-msg {
    text-align: center;
    color: black;
    font-size: 10px;
  }

  .uploader-panel {
    display: flex;
    padding: 7px 15px;
    flex-direction: column;
    align-items: center;
    box-sizing: border-box;
    height: 395px;
    transition: .3s linear;
    width: 700px;
    position: fixed;
    bottom: 0;
    right: 10px;
    z-index: 9999;
    border-radius: 5px;
    box-shadow:0 0 3px rgba(0,0,0,0.3);
    background: white;
  }

  .uploader-panel .up-header, .uploader-panel .up-items {
    width: 100%;
    overflow: hidden;
    position: relative;
  }

  .up-items {
    max-height: 350px;
    overflow-y: auto !important;
  }

  .up-header {
    padding: 7px 0;
    font-size: 16px;
    display: flex;
    align-items: center;
  }

  .up-items-info, .up-item {
    font-size: 13px;
    width: 100%;
    display: flex;
    color: #515a6e;
    align-items: center;
  }

  .up-items-info span, .up-item span {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    -o-text-overflow: ellipsis;
  }

  .filename {
    flex: 3;
  }

  .size {
    flex: 1;
  }

  .speed, .state {
    flex: 2;
  }

  .up-actions {
    flex: 1;
  }

  .up-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: 9px;
  }

  .up-actions svg {
    display: inline-block;
    cursor: pointer;
  }

  .up-item {
    padding: 6px 0;
    font-size: 14px;
  }

  .progress {
    position: absolute;
    width: 100%;
    transform: translateX(-10%);
    height: 30px;
    left: 0;
    z-index: -1;
    top: 18px;
  }

  .up-items, .up-item, .up-header, .up-items-info {
    padding-left: 3px;
  }
  .up-items-info,.up-item{
    text-align: left;
  }

  .progress.md5 {
    background-color: rgb(234, 254, 244);
  }

  .progress.up {
    background-color: rgb(227, 243, 255);
  }

  .progress.error {
    background-color: rgba(255, 222, 219, 0.64);;
  }

  .progress.success {
    background-color: rgba(220, 255, 219, 0.78);
  }
</style>
